package com.soccertgc.core.game.client 
{
	import com.soccertgc.comm.Call;
	import com.soccertgc.comm.CallErrorEvent;
	import com.soccertgc.comm.CallEvent;
	import com.soccertgc.comm.CallFactory;
	import com.soccertgc.comm.CallFailEvent;
	import com.soccertgc.comm.CallSuccessEvent;
	import com.soccertgc.core.game.client.events.GameClientErrorEvent;
	import com.soccertgc.core.game.client.events.GameClientEvent;
	import com.soccertgc.core.game.client.events.GameClientRequestEvent;
	import com.soccertgc.core.game.requests.GameRequest;
	import com.soccertgc.core.game.requests.GameRequestResult;
	import com.soccertgc.logging.Logger;
	import com.soccertgc.utils.DateUtils;
	import com.soccertgc.utils.JsonUtils;
	import com.soccertgc.utils.StringUtils;
	
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	public class GameClient extends EventDispatcher
	{
		
		// -------------------------------------------------------------------------------
		// CONSTANTS
		// -------------------------------------------------------------------------------
		
		// Logging
		private static const		LOG_MARKER:String						= "GAME CLIENT > ";
		private static const		LOG_MARKER_WORKER:String				= LOG_MARKER + "WORKER >";
		private static const		LOG_MARKER_REQUEST:String				= LOG_MARKER + "REQUEST > ";
		private static const		LOG_MARKER_HEARTBEAT:String				= LOG_MARKER + "HEARTBEAT > ";
		private static const		LOG_MARKER_CONNECTION:String			= LOG_MARKER + "CONNECTION > ";
		
		// Debug keys
		public static const			DEBUG_KEY:String						= "core.game.client";
		public static const			DEBUG_KEY_WORKER:String					= "core.game.client.worker";
		public static const			DEBUG_KEY_REQUEST:String				= "core.game.client.request";
		public static const			DEBUG_KEY_HEARTBEAT:String				= "core.game.client.heartbeat";
		public static const			DEBUG_KEY_CONNECTION:String				= "core.game.client.connection";
		
		// Heartbeat contants
		private static const		HEARTHBEAT_COUNTER_MIN:int				= 1;
		private static const		HEARTHBEAT_FAIL_TO_DISCONNECT:int		= 3;
		
		// Request contants
		private static const		REQUEST_RETRY_MAX:int					= 3;
		
		// -------------------------------------------------------------------------------
		// FIELDS
		// -------------------------------------------------------------------------------
		
		// Client options
		private var _options:GameClientOptions;
		// Status
		private var _status:GameClientStatus = GameClientStatus.NOT_CONNECTED;
		// Heartbeat status
		private var _heartbeatCount:int;
		private var _heartbeatFailCount:int;
		private var _heartbeatDate:Date;
		// Requests
		private var _request:GameRequest;
		private var _requestRetries:uint;
		private var _requestRetryDate:Date;
		private var _requests:Vector.<GameRequest>; // (currently only one request at time, but is ready for a requests queue)
		// Polling status
		private var _pollingCount:int;
		private var _pollingDate:Date;
		// Calls
		private var _requestCall:Call;
		private var _hearbeatCall:Call;
		// Timers
		private var _worker:Timer;
		
		// -------------------------------------------------------------------------------
		// CONSTRUCTOR
		// -------------------------------------------------------------------------------
		
		public function GameClient(options:GameClientOptions)
		{
			// Options
			_options = options;
			// Requests list
			_requests = new Vector.<GameRequest>();
			// Calls init
			_hearbeatCall	= heartbeatCallCreate();
			_requestCall 	= null;
			// Worker
			_worker = new Timer(_options.workerDelay, 0); // 0 = infinite
			_worker.addEventListener(TimerEvent.TIMER, workerHandler);
			// Init data
			reset();
		}
		
		// -------------------------------------------------------------------------------
		// PROPERTIES
		// -------------------------------------------------------------------------------
		
		public function get options():GameClientOptions
		{
			return _options;
		}
		
		public function get status():GameClientStatus
		{
			return _status;
		}
		
		public function get isConnected():Boolean
		{
			return _status.id > GameClientStatus.CONNECTING.id;
		}
		
		public function get isConnecting():Boolean
		{
			return _status == GameClientStatus.CONNECTING;
		}
		
		public function get isInPolling():Boolean
		{
			return	_status == GameClientStatus.POLLING &&
					_request != null && 
					_request.isPollingRequest;
		}
		
		public function get isRunning():Boolean
		{
			return _request != null;
		}
		
		public function get hasRequests():Boolean
		{
			return	_requests.length > 0;
		}
		
		public function get hasRequestsToRetry():Boolean
		{
			return	hasRequests && isRunning && _request.retries > 0;
		}
		
		// -------------------------------------------------------------------------------
		// PRIVATE METHODS
		// -------------------------------------------------------------------------------
		
		
		// Heartbear handling
		
		protected function heartbeat():void
		{
			// STATUS > HEARTBEAT (only if connected)
			if (isConnected) setStatus(GameClientStatus.HEARTBEAT);
			// Sets the heartbeat date
			_heartbeatDate = new Date();
			// Executes the heartbeat call
			_hearbeatCall.clear();
			// Updates call parameters
			heartbeatCallUpdate();
			// Execute the call
			_hearbeatCall.execute();
		}
		
		protected function heartbeatSuccess():void
		{
			try
			{
				// Resets the heartbeat fail counter
				_heartbeatFailCount = 0;
				// Check if this is the first heartbeat, so the connection is no yet opened
				// and client status is CONNECTING
				if (isConnecting)
				{
					// Flag connection started and init the heartbeat counter
					_heartbeatCount = HEARTHBEAT_COUNTER_MIN;
					// STATUS > READY (connetion up)
					setStatus(GameClientStatus.READY);
					// DEBUG > Connection start
					if (Logger.canDebug(DEBUG_KEY_CONNECTION)) Logger.debug(LOG_MARKER_CONNECTION + "start");
					// Dispatch EVENT_CONNECT event
					dispatchConnectEvent();
				}
				else
				{
					// Increment the heartbeat
					_heartbeatCount++;
					// STATUS > READY (connetion up)
					setStatus(GameClientStatus.READY);
				}
				// Restart the worker
				workerStart();
			}
			catch (e:Error)
			{
				// Manage unexpected error
				manageUnexpectedError(LOG_MARKER_HEARTBEAT + "heartbeatSuccess() unexpected error", e);
			}
		}
		
		protected function heartbeatFailed():void
		{
			try
			{
				// Incremente the error counter
				_heartbeatFailCount++;
				// Check if the maximum fail count is reached
				if (_heartbeatFailCount < HEARTHBEAT_FAIL_TO_DISCONNECT)
				{
					// Restart the worker
					workerStart();
				}
				else
				{
					// If the client is already connected
					if (isConnected)
					{
						// Force disconnection
						connectionClose(false);
					}
					else
					{
						// Connection failed
						connectionFailed();
					}
				}
			}
			catch (e:Error)
			{
				// Manage unexpected error
				manageUnexpectedError(LOG_MARKER_HEARTBEAT + "heartbeatFailed() unexpected error", e);
			}
		}
		
		protected function heartbeatCallSuccess(e:CallSuccessEvent):void
		{
			// Heartbeat ok
			heartbeatSuccess();
			// LOG
			if (Logger.canDebug(DEBUG_KEY_HEARTBEAT)) Logger.debug(LOG_MARKER_HEARTBEAT + "Heartbeat (counter:{0})", _heartbeatCount);
		}
		
		protected function heartbeatCallError(e:CallErrorEvent):void
		{
//			// Compose the error message
//			var msg:String = LOG_MARKER_HEARTBEAT + "Heartbeat call error (prog: {0} error prog: {1}, type: {2}, message: {3})";
//			// Log error
//			Logger.error(msg, _heartbeatCount, _heartbeatFailCount, e.call.error.type, e.call.error.message);
			
			
			// Compose the error message
			var msg:String = LOG_MARKER_HEARTBEAT + "Heartbeat call error (prog: {0} error prog: {1}, type: {2}, message: {3}) | dump: {4}";
			// Log error
			Logger.error(msg, _heartbeatCount, _heartbeatFailCount, e.call.error.type, e.call.error.message, e.call.toString());
			// Manage heartbeat failed
			heartbeatFailed();
		}
		
		protected function heartbeatCallFail(e:CallFailEvent):void
		{
//			// Compose the error message
//			var msg:String = LOG_MARKER_HEARTBEAT + "Heartbeat call failed (prog: {0} error prog: {1}, type: {2}, message: {3})";
//			// Log error
//			Logger.error(msg, e.error, _heartbeatCount, _heartbeatFailCount, e.call.error.type, e.call.error.message);
			
			// Compose the error message
			var msg:String = LOG_MARKER_HEARTBEAT + "Heartbeat call failed (prog: {0}, error prog: {1}, type: {2}, message: {3}) | dump: {4}";
			// Log error
			Logger.error(msg, e.error, _heartbeatCount, _heartbeatFailCount, e.call.error.type, e.call.error.message, e.toString());
			
			// Manage heartbeat failed
			heartbeatFailed();
		}
		
		protected function heartbeatCallCreate():Call
		{
			// Local
			var call:Call;
			call = new Call(CallFactory.CALL_GAME_HEARTBEAT, true);
			// Parameters
			call.params = { userID: "", password: "", last:	0 };
			// Event handlers
			call.addEventListener(CallEvent.EVENT_CALL_SUCCESS,	heartbeatCallSuccess);
			call.addEventListener(CallEvent.EVENT_CALL_FAIL, 	heartbeatCallFail);
			call.addEventListener(CallEvent.EVENT_CALL_ERROR, 	heartbeatCallError);
			// Returns the call
			return call;
		}
		
		protected function heartbeatCallUpdate():void
		{
			_hearbeatCall.params.userID 	= _options.getParamAsString(GameClientOptions.PARAM_USER_ID);
			_hearbeatCall.params.password 	= _options.getParamAsString(GameClientOptions.PARAM_PASSWORD);
			_hearbeatCall.params.last		= _heartbeatCount;
		}
		
		
		// Requests handling
		
		protected function request(req:GameRequest):void
		{
			// Clears the current request call (if any)
			requestCallClear();
			// Creates the ation for the call
			var call:Call = requestCallCreate(req);
			// Set the current request and call
			_request = req;
			_requestCall = call;
			// If is a polling request, store the date and resets the counter
			if (_request.isPollingRequest)
			{
				// Initialize polling status
				_pollingCount = 0;
				_pollingDate = new Date();
				// STATUS > POLLING
				setStatus(GameClientStatus.POLLING);
			}
			else
			{
				// STATUS > REQUEST
				setStatus(GameClientStatus.REQUEST);
			}
			// DEBUG > Trace request call
			if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "Execute {0} (id: {1})", _request.phase.name, _request.id);
			// Executes the request call
			_requestCall.execute();
		}
		
		protected function requestRetry():void
		{
			// Clear and reset the call
			_requestCall.clear();
			// Executes the request again
			_requestCall.execute();
			// DEBUG > Trace request retry call
			if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "Retry {0} (id: {1}) - retry: {2}", _request.phase.name, _request.id, _request.retries);
		}
		
		protected function requestExecuted(call:Call):void
		{
			try
			{
				// Retrieve the current request
				var request:GameRequest = _request;
				// Creates the result
				var result:GameRequestResult = new GameRequestResult(call.data);
				// Process the request results
				request.process(result);
				// Check if the request is in error
				if (!request.isInError)
				{
					// Check if the current request is a polling request
					if (request.waiting)
					{
						// Check if the status is POLLING
						if (!isInPolling) throw new Error("Request " + request.phase.name + " (id:" + request.id + ") WAIT result but client not in POLLING!");
						
						// Nothing to do, not clear the request, only restarts the worker
						
						// DEBUG > Trace polling request call
						if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "WAITING {0} (id: {1}) {2} retries", request.phase, request.id, _pollingCount);
						// STATUS > polling
						setStatus(GameClientStatus.POLLING);
						// Dispatch EVENT_REQUEST_WAITING event
						dispatchRequestWaitingEvent(request);
					}
					else
					{
						// DEBUG > Trace polling request call
						if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "EXEC {0} success (id: {1}) data: {2}", request.phase, request.id, JsonUtils.encode(request.result.data));
						// STATUS > ready
						setStatus(GameClientStatus.READY);
						// Resets the current request, call and queue
						requestReset();
						// Dispatch EVENT_REQUEST_EXECUTED event
						dispatchRequestExecutedEvent(request);
					}
				}
				else
				{
					// DEBUG > Trace polling request call
					if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "EXEC {0} FAILED (id: {1}) error {2} (code:{3})", request.phase, request.id, request.result.error.message, request.result.error.code);
					// STATUS > ready
					setStatus(GameClientStatus.READY);
					// Resets the current request, call and queue
					requestReset();
					// Dispatch EVENT_REQUEST_FAILED event
					dispatchRequestFailedEvent(request);
				}
				// Restart the worker if the client is still connected
				if (isConnected) workerStart();
			}
			catch (e:Error)
			{
				// Manage unexpected error
				manageUnexpectedError(LOG_MARKER_REQUEST + "requestExecuted() in error", e);
			}
		}
		
		protected function requestFailed(message:String):void
		{
			// Resets the current request, call and queue
			requestReset();
			// Request in error, dispatch EVENT_ERROR 
			dispatchErrorEvent(new Error(message));
			// Force disconnection
			connectionClose(false);
		}
		
		protected function requestFailedWithRetry():void
		{
			// Resets the request to execute it again and save the time
			_request.retry();
			_requestRetryDate = new Date();
			// Restart the worker if the client is still connected
			if (isConnected) workerStart();
			// DEBUG > Trace request retry
			if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + " request failed, but star RETRY");
		}
		
		protected function requestReset():void
		{
			// Removes the request from the queue
			_requests.splice(_requests.indexOf(_request), 1);
			// Clear the reference to the current request
			_request = null;
			_requestRetries = 0;
			_requestRetryDate = null;
			// Clear and reset the call
			_requestCall.clear();
			_requestCall = null;
			// DEBUG > Trace polling request call
			if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "reset done!");
		}
		
		protected function requestCallSuccess(e:CallSuccessEvent):void
		{
			// LOG
			if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "Request {0} (id: {1}) succedeed - url: {2}", _request.phase.name, _request.id, e.call.toUrl());
			// Request executed
			requestExecuted(e.call);
		}
		
		protected function requestCallError(e:CallErrorEvent):void
		{
			// Compose the error message
			var msg:String = LOG_MARKER_REQUEST + "Request {0} (id: {1}) in error: {2} (type: {3}, code:{4}, url:{5})";
			// Log error
			Logger.error(msg, _request.phase.name, _request.id, e.call.error.message, e.call.error.type.name, e.call.error.code, e.call.toUrl());
			// Manage request failed
			requestFailed(StringUtils.substitute(msg, _request.phase.name, _request.id, e.call.error.message, e.call.error.type.name, e.call.error.code, e.call.toUrl()));
		}
		
		protected function requestCallFail(e:CallFailEvent):void
		{
			// Compose the error message
			var msg:String = LOG_MARKER_REQUEST + "Request {0} (id: {1}) failed : {2} (type: {3}, message: {4}, url: {5})";
			// Log error
			Logger.error(msg, _request.phase.name, _request.id, e.error, e.call.error.type, e.call.error.message, e.call.toUrl());
			// Check if the call fail con be retried and if the current request has cross the retries limit
			if (e.canRetry && _request.retries < REQUEST_RETRY_MAX)
			{
				// Retry to execute the request
				requestFailedWithRetry();
			}
			else
			{
				// Manage request failed
				requestFailed(StringUtils.substitute(msg, _request.phase.name, _request.id, e.error, e.call.error.type, e.call.error.message, e.call.toUrl()));
			}
		}
		
		protected function requestCallClear():void
		{
			// Clear previous call and create the new call to use
			if (_requestCall != null)
			{
				// Cancels any execution
				if (_requestCall.executing) _requestCall.cancel();
				// Clear call and nulls the reference
				_requestCall.clear();
				_requestCall = null;
			}
			else
			{
				// WARNING > Trace useless call
				Logger.warn(LOG_MARKER_REQUEST + "requestCallClear() called without any request to clear!");
			}
		}
			
		protected function requestCallCreate(request:GameRequest):Call
		{
			// Local
			var call:Call;
			// Creates the call from the request
			call = request.createCall();
			// Force request parameters using the client options
			request.params.gameID 	= int(_options.getParam(GameClientOptions.PARAM_GAME_ID));		// _options.params[GameClientOptions.PARAM_GAME_ID];
			request.params.userID 	= _options.getParamAsString(GameClientOptions.PARAM_USER_ID);	// _options.params[GameClientOptions.PARAM_USER_ID];
			request.params.password	= _options.getParamAsString(GameClientOptions.PARAM_PASSWORD);	// _options.params[GameClientOptions.PARAM_PASSWORD];
			// Sets the handlers for call events
			call.addEventListener(CallEvent.EVENT_CALL_SUCCESS, requestCallSuccess);
			call.addEventListener(CallEvent.EVENT_CALL_ERROR, 	requestCallError);
			call.addEventListener(CallEvent.EVENT_CALL_FAIL, 	requestCallFail);
			// DEBUG > log the call created
			if (Logger.canDebug(DEBUG_KEY_REQUEST)) 
			{
				Logger.debug(LOG_MARKER_REQUEST + "create call for request {0} - url: {1}", request.toString(), call.toUrl());
			}
			// Returns the call
			return call;
		}	
		
		
		// request polling handling
		
		protected function polling():void
		{
			// Increments the counter
			_pollingCount++;
			// Sets the current polling date
			_pollingDate = new Date();
			// DEBUG > Trace polling request call
			if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "Polling {0} (id: {1}, count: {2})", _request.phase.name, _request.id, _pollingCount);
			// Clears the current request call
			_requestCall.clear();
			// Executes the request again
			_requestCall.execute();
		}
		
		
		// Generic handling
		
		protected function reset():void
		{
			// Set base status
			_heartbeatCount		= 0;
			_heartbeatFailCount	= 0;
			_heartbeatDate		= null;
			// Clear the call request
			if (_requestCall != null)
			{
				_requestCall.clear();
				_requestCall = null;				
			}
			// Remove current request reference
			_request = null;
			// Empties the request queue
			_requests.splice(0, _requests.length); 
			// Polling
			_pollingCount = 0;
			_pollingDate = null;
			// STATUS > NOT CONNECTED
			setStatus(GameClientStatus.NOT_CONNECTED);
			// DEBUG > Trace polling request call
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "RESET > done");
		}
		
		protected function setStatus(newStatus:GameClientStatus):void
		{
			if (_status != newStatus)
			{
				var oldStatus:GameClientStatus = _status;
				_status = newStatus;
				// DEBUG > Trace status change
				if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + "STATUS > changed {0} to {1}", oldStatus, newStatus);
			}
		}
		
		protected function getHeartbeatIntervall():uint
		{
			return	!isConnecting ? _heartbeatFailCount > 0 ? _options.intervalHeartbeatRetry
														    : _options.intervalHeartbeat
								  : _options.intervalHeartbeat;
		}
		
		protected function connectionFailed():void
		{
			// Reset the status
			reset();
			// Stops the worker
			workerStop();
			// Dispatch EVENT_CONNECTION_FAILED event
			dispatchConnectionFailedEvent();
		}
		
		protected function connectionClose(byError:Boolean):void
		{
			// Disconnection
			disconnect(byError);
			// If the focing derived loosing the heartbeat, dispatch EVENT_CONNECTION_LOST event
			if (!byError) dispatchConnectionLostEvent();
			// DEBUG > Connection closed
			if (Logger.canDebug(DEBUG_KEY)) Logger.debug(LOG_MARKER + " FORCED DISCONNECTION!");
		}
				
		/**
		 * Game goes in error for unexpected problem or exception 
		 */		
		protected function manageUnexpectedError(message:String, e:Error):void
		{
			// Compose the error message
			var msg:String = "";
			msg += LOG_MARKER;
			if (!StringUtils.isEmpty(message))
			{
				msg += " " + message;
			}
			// ERROR > log the complete error
			Logger.error(msg, e);
			// Dispatch EVENT_GAME_ERROR
			dispatchErrorEvent(e);
			// Force the disconnection
			connectionClose(true);
		}
		
		
		// Worker handling
		
		protected function workerStart():void
		{
			// Stops the worker
			_worker.start();
			// DEBUG > Log worker started
			if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER + "WORKER > started");
		}
		
		protected function workerStop():void
		{
			// Stops the worker
			_worker.stop();
			// DEBUG > Log worker stopped
			if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER + "WORKER > stopped");
		}
			
		protected function workerHandler(e:TimerEvent):void
		{
			var restartWorker:Boolean = false;
			// Safe execution
			try
			{
				// Stops the execution of the timer
				workerStop();
				// Flag to check heartbeat is ok
				var connectionValid:Boolean = _heartbeatFailCount == 0;
				// Check if the client must retry the last request
				if (connectionValid && hasRequestsToRetry)
				{
					// Retrieve the elapsed time from last poll
					var requestRetryElapsed:Number = DateUtils.elapsed(_requestRetryDate);
					// Check the elapsed ms from the requets retry
					if (requestRetryElapsed > _options.intervalRequestRetry)
					{
						requestRetry();
					}
					else
					{
						// DEBUG > Polling intervall not reached
						if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER_WORKER + "request retry not executed, elapsed: {0}", requestRetryElapsed);
						// Ok restart worker
						restartWorker = true;
					}
				}
				// Check the client is currently polling
				else if (connectionValid && isInPolling)
				{
					// Retrieve the elapsed time from last poll
					var pollingElapsed:Number = DateUtils.elapsed(_pollingDate);
					// Check the elapsed ms from the last poll
					if (pollingElapsed > _options.intervalPolling)
					{
						polling();
					}
					else
					{
						// DEBUG > Polling intervall not reached
						if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER_WORKER + "polling not executed, elapsed: {0}", pollingElapsed);
						// Ok restart worker
						restartWorker = true;
					}
				}
				// Check if any request is in the queue
				else if (connectionValid && hasRequests)
				{
					// Retrieve oldest request
					var r:GameRequest = _requests[0];
					// DEBUG > Request to be esxecuted
					if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER_WORKER + "request {0} being executed", r.toString());
					// Executes the heartbeat call
					request(r);
				}
				// Executes the heartbeat
				else
				{
					// Retrieve the elapsed time from last heartbeat
					var elapsedHeartbeat:Number = DateUtils.elapsed(_heartbeatDate);
					// Check the elapsed ms from the last heartbeat
					if (elapsedHeartbeat > getHeartbeatIntervall())
					{
						// DEBUG > signal hearbeat retry
						if (!connectionValid) if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER + "WORKER > heartbeat retry {0}", _heartbeatFailCount);
						// Executes the heartbeat
						heartbeat();
					}
					else
					{
						// DEBUG > Polling intervall not reached
						if (Logger.canDebug(DEBUG_KEY_WORKER)) Logger.debug(LOG_MARKER_WORKER + "heartbeat not executed, elapsed: {0}", elapsedHeartbeat);
						// Ok restart worker
						restartWorker = true;
					}
				}
				// Restart the worker
				if (restartWorker) workerStart();
			}
			catch (e:Error)
			{
				// Manage unexpected error
				manageUnexpectedError("WORKER > worker handler in error", e);
			}
		}
		
		
		// Event dispatcher
		
		protected function dispatchConnectEvent():void
		{
			dispatchEvent(new GameClientEvent(GameClientEvent.EVENT_CONNECT, this));
		}
		
		protected function dispatchDisconnectEvent():void
		{
			dispatchEvent(new GameClientEvent(GameClientEvent.EVENT_DISCONNECT, this));
		}
		
		protected function dispatchConnectionLostEvent():void
		{
			dispatchEvent(new GameClientEvent(GameClientEvent.EVENT_CONNECTION_LOST, this));
		}
		
		protected function dispatchConnectionFailedEvent():void
		{
			dispatchEvent(new GameClientEvent(GameClientEvent.EVENT_CONNECTION_FAILED, this));
		}
		
		protected function dispatchRequestExecutedEvent(request:GameRequest):void
		{
			dispatchEvent(new GameClientRequestEvent(GameClientEvent.EVENT_REQUEST_EXECUTED, this, request));
		}
		
		protected function dispatchRequestFailedEvent(request:GameRequest):void
		{
			dispatchEvent(new GameClientRequestEvent(GameClientEvent.EVENT_REQUEST_FAILED, this, request));
		}
		
		protected function dispatchRequestWaitingEvent(request:GameRequest):void
		{
			dispatchEvent(new GameClientRequestEvent(GameClientEvent.EVENT_REQUEST_WAITING, this, request));
		}
		
		protected function dispatchErrorEvent(e:Error):void
		{
			dispatchEvent(new GameClientErrorEvent(this, e));
		}
		
		// -------------------------------------------------------------------------------
		// PUBLIC METHODS
		// -------------------------------------------------------------------------------
		
		/**
		 * Starts the client connection to the server: sends a heartbeat call and wait the response, 
		 * if successful, dispatch a Connect event, if fails retries for HEARTHBEAT_FAIL_TO_DISCONNECT times
		 * after that ConnectionFailed event di dispatched
		 */		
		public function connect():void
		{
			// Check connecting
			if (isConnecting) throw new Error("GameClient is connecting, connection can't be opened again!");
			// Check connection
			if (isConnected) throw new Error("GameClient is already connected, connection can't be opened again!");
			// Reset the status
			reset();
			// STATUS > Connectiong
			setStatus(GameClientStatus.CONNECTING);
			// DEBUG > Connection starting
			if (Logger.canDebug(DEBUG_KEY_CONNECTION)) Logger.debug(LOG_MARKER_CONNECTION + "starting...");
			// Starts the heartbeat to check the connection
			heartbeat();
		}
		
		/**
		 * Disconnects the client from the server: cancel any pending call, stops the worker timer
		 * and restets the status. Finally dispatch a Disconnect event
		 * @param dispatchEvent Flag to dispach a disconnect event (not used to handle the connection failure) 
		 */		
		public function disconnect(dispatchEvent:Boolean = true):void
		{
			// Switch basing on the current status
			switch(_status)
			{
				// Heartbeat
				case GameClientStatus.CONNECTING:
				case GameClientStatus.HEARTBEAT:
				{
					// Kills the heartbeat
					_hearbeatCall.cancel();										
					break;
				}
				// request
				case GameClientStatus.REQUEST:
				case GameClientStatus.POLLING:
				{
					// Kills the request call
					if (_requestCall != null && _requestCall.executing) _requestCall.cancel();										
					break;
				}
			}
			// Kills the worker if running
			if (_worker.running)
			{
				workerStop();
			}
			// Reset the status
			reset();
			// Dispatch EVENT_DISCONNECT event
			if (dispatchEvent) dispatchDisconnectEvent();
			// DEBUG > Client disconnection
			if (Logger.canDebug(DEBUG_KEY_CONNECTION)) Logger.debug(LOG_MARKER_CONNECTION + "stop");
		}
		
		/**
		 * Executes a request, creates the request call and wait for server response: handler internally
		 * the request result parsing and handling, dispatching request events 
		 * @param request 
		 */		
		public function execute(request:GameRequest):void
		{
			// LT_TODO: check connection open
			if (!isConnected) throw new Error("GameClient is not connected!");
			// Push the request in the list
			_requests.push(request);
			// DEBUG > Remove the request from the queue
			if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "{0} request added", request);
		}
		
		/**
		 * Cancels a client phase currently executing. 
		 * Is used when the engine reports a timeout 
		 * during the client polling for resolve operations 
		 * @param request Request to cancel
		 * @return Cacenlled request
		 */		
		public function cancel(request:GameRequest = null):GameRequest
		{
			// Calncel done flag
			var done:Boolean = false;
			// If no request is passed, use the current request
			if (request == null) request = _request;
			// Check if we have a running requets and is the current request
			if (_request != null && _request == request)
			{
				// Cancels the call and remove the request
				requestCallClear();
				// Cancel done
				done = true;
				// DEBUG > Remove the request from the queue
				if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "{0} request cancelled and reset", request);
			}
			// If a valid request is passed and we dont already cancel the request
			if (request != null && !done)
			{
				// Search the request in the queue
				for (var i:int = 0; i < _requests.length; i++)
				{
					if (_requests[i].equals(request))
					{
						// DEBUG > Remove the request from the queue
						if (Logger.canDebug(DEBUG_KEY_REQUEST)) Logger.debug(LOG_MARKER_REQUEST + "{0} request removed from the queue", request);
						// Remove the request from the list
						_requests.splice(i, 1);
					}
				}
			}
			// Returns the cancelled request
			return request;
		}
		
		/**
		 * Forces the client to starts a polling for passive requests 
		 * @param request Passive Game request to starts the polling for
		 */		
		public function forcePolling(request:GameRequest):void
		{
			// LT_TODO: verificare se la richiesta si può generare fuori, altrimenti
			// passare GamePhase
			
			_status = GameClientStatus.POLLING;
		}
		
	}
}